/*
 * Licensed to Elastic Search and Shay Banon under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Elastic Search licenses this
 * file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package net.csdn.modules.compress.lzf;

import java.lang.ref.SoftReference;

/**
 * Simple helper class to encapsulate details of basic buffer
 * recycling scheme, which helps a lot (as per profiling) for
 * smaller encoding cases.
 *
 * @author tatu
 */
public class BufferRecycler {
    private final static int MIN_ENCODING_BUFFER = 4000;

    private final static int MIN_OUTPUT_BUFFER = 8000;

    /**
     * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftRerefence}
     * to a {@link BufferRecycler} used to provide a low-cost
     * buffer recycling for buffers we need for encoding, decoding.
     */
    final protected static ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef
            = new ThreadLocal<SoftReference<BufferRecycler>>();


    private byte[] _inputBuffer;
    private byte[] _outputBuffer;

    private byte[] _decodingBuffer;
    private byte[] _encodingBuffer;

    private int[] _encodingHash;


    /**
     * Accessor to get thread-local recycler instance
     */
    public static BufferRecycler instance() {
        SoftReference<BufferRecycler> ref = _recyclerRef.get();
        BufferRecycler br = (ref == null) ? null : ref.get();
        if (br == null) {
            br = new BufferRecycler();
            _recyclerRef.set(new SoftReference<BufferRecycler>(br));
        }
        return br;
    }

    public static void clean() {
        _recyclerRef.remove();
    }

    /*
    ///////////////////////////////////////////////////////////////////////
    // Buffers for encoding (output)
    ///////////////////////////////////////////////////////////////////////
     */

    public byte[] allocEncodingBuffer(int minSize) {
        byte[] buf = _encodingBuffer;
        if (buf == null || buf.length < minSize) {
            buf = new byte[Math.max(minSize, MIN_ENCODING_BUFFER)];
        } else {
            _encodingBuffer = null;
        }
        return buf;
    }

    public void releaseEncodeBuffer(byte[] buffer) {
        if (_encodingBuffer == null || buffer.length > _encodingBuffer.length) {
            _encodingBuffer = buffer;
        }
    }

    public byte[] allocOutputBuffer(int minSize) {
        byte[] buf = _outputBuffer;
        if (buf == null || buf.length < minSize) {
            buf = new byte[Math.max(minSize, MIN_OUTPUT_BUFFER)];
        } else {
            _outputBuffer = null;
        }
        return buf;
    }

    public void releaseOutputBuffer(byte[] buffer) {
        if (_outputBuffer == null || buffer.length > _outputBuffer.length) {
            _outputBuffer = buffer;
        }
    }

    public int[] allocEncodingHash(int suggestedSize) {
        int[] buf = _encodingHash;
        if (buf == null || buf.length < suggestedSize) {
            buf = new int[suggestedSize];
        } else {
            _encodingHash = null;
        }
        return buf;
    }

    public void releaseEncodingHash(int[] buffer) {
        if (_encodingHash == null || buffer.length > _encodingHash.length) {
            _encodingHash = buffer;
        }
    }

    /*
    ///////////////////////////////////////////////////////////////////////
    // Buffers for decoding (input)
    ///////////////////////////////////////////////////////////////////////
     */

    public byte[] allocInputBuffer(int minSize) {
        byte[] buf = _inputBuffer;
        if (buf == null || buf.length < minSize) {
            buf = new byte[Math.max(minSize, MIN_OUTPUT_BUFFER)];
        } else {
            _inputBuffer = null;
        }
        return buf;
    }

    public void releaseInputBuffer(byte[] buffer) {
        if (_inputBuffer == null || buffer.length > _inputBuffer.length) {
            _inputBuffer = buffer;
        }
    }

    public byte[] allocDecodeBuffer(int size) {
        byte[] buf = _decodingBuffer;
        if (buf == null || buf.length < size) {
            buf = new byte[size];
        } else {
            _decodingBuffer = null;
        }
        return buf;
    }

    public void releaseDecodeBuffer(byte[] buffer) {
        if (_decodingBuffer == null || buffer.length > _decodingBuffer.length) {
            _decodingBuffer = buffer;
        }
    }

}
